home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Unix / Shells / tcsh / Source / tw.parse.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-02-21  |  43.4 KB  |  1,817 lines

  1. /* $Header: /u/christos/src/tcsh-6.03/RCS/tw.parse.c,v 3.42 1992/10/18 00:30:39 christos Exp $ */
  2. /*
  3.  * tw.parse.c: Everyone has taken a shot in this futile effort to
  4.  *           lexically analyze a csh line... Well we cannot good
  5.  *           a job as good as sh.lex.c; but we try. Amazing that
  6.  *           it works considering how many hands have touched this code
  7.  */
  8. /*-
  9.  * Copyright (c) 1980, 1991 The Regents of the University of California.
  10.  * All rights reserved.
  11.  *
  12.  * Redistribution and use in source and binary forms, with or without
  13.  * modification, are permitted provided that the following conditions
  14.  * are met:
  15.  * 1. Redistributions of source code must retain the above copyright
  16.  *    notice, this list of conditions and the following disclaimer.
  17.  * 2. Redistributions in binary form must reproduce the above copyright
  18.  *    notice, this list of conditions and the following disclaimer in the
  19.  *    documentation and/or other materials provided with the distribution.
  20.  * 3. All advertising materials mentioning features or use of this software
  21.  *    must display the following acknowledgement:
  22.  *    This product includes software developed by the University of
  23.  *    California, Berkeley and its contributors.
  24.  * 4. Neither the name of the University nor the names of its contributors
  25.  *    may be used to endorse or promote products derived from this software
  26.  *    without specific prior written permission.
  27.  *
  28.  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
  29.  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  30.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  31.  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
  32.  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  33.  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  34.  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  35.  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  36.  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  37.  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  38.  * SUCH DAMAGE.
  39.  */
  40. #include "sh.h"
  41.  
  42. RCSID("$Id: tw.parse.c,v 3.42 1992/10/18 00:30:39 christos Exp $")
  43.  
  44. #include "tw.h"
  45. #include "ed.h"
  46. #include "tc.h"
  47.  
  48. #define EVEN(x) (((x) & 1) != 1)
  49.  
  50. #define DOT_NONE    0    /* Don't display dot files        */
  51. #define DOT_NOT        1    /* Don't display dot or dot-dot        */
  52. #define DOT_ALL        2    /* Display all dot files        */
  53.  
  54. /*  TW_NONE,           TW_COMMAND,     TW_VARIABLE,    TW_LOGNAME,    */
  55. /*  TW_FILE,           TW_DIRECTORY,   TW_VARLIST,     TW_USER,        */
  56. /*  TW_COMPLETION,     TW_ALIAS,       TW_SHELLVAR,    TW_ENVVAR,    */
  57. /*  TW_BINDING,        TW_WORDLIST,    TW_LIMIT,       TW_SIGNAL    */
  58. /*  TW_JOB,           TW_EXPLAIN,     TW_PATHNAME,    TW_TEXT        */
  59. static void (*tw_start_entry[]) __P((DIR *, Char *)) = {
  60.     tw_file_start,     tw_cmd_start,   tw_var_start,   tw_logname_start, 
  61.     tw_file_start,     tw_file_start,  tw_vl_start,    tw_logname_start, 
  62.     tw_complete_start, tw_alias_start, tw_var_start,   tw_var_start,     
  63.     tw_bind_start,     tw_wl_start,    tw_limit_start, tw_sig_start,
  64.     tw_job_start,      tw_file_start,  tw_file_start,  tw_file_start
  65. };
  66.  
  67. static Char * (*tw_next_entry[]) __P((Char *, int *)) = {
  68.     tw_file_next,      tw_cmd_next,    tw_var_next,    tw_logname_next,  
  69.     tw_file_next,      tw_file_next,   tw_var_next,    tw_logname_next,  
  70.     tw_var_next,       tw_var_next,    tw_shvar_next,  tw_envvar_next,   
  71.     tw_bind_next,      tw_wl_next,     tw_limit_next,  tw_sig_next,
  72.     tw_job_next,       tw_file_next,   tw_file_next,   tw_file_next
  73. };
  74.  
  75. static void (*tw_end_entry[]) __P((void)) = {
  76.     tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_logname_end,
  77.     tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_logname_end, 
  78.     tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_dir_end,
  79.     tw_dir_end,        tw_dir_end,     tw_dir_end,    tw_dir_end,
  80.     tw_dir_end,           tw_dir_end,     tw_dir_end,    tw_dir_end
  81. };
  82.  
  83. /* #define TDEBUG */
  84.  
  85. /* Set to TRUE if recexact is set and an exact match is found
  86.  * along with other, longer, matches.
  87.  */
  88. int non_unique_match = FALSE;
  89. static bool SearchNoDirErr = 0;    /* t_search returns -2 if dir is unreadable */
  90.  
  91. /* state so if a completion is interrupted, the input line doesn't get
  92.    nuked */
  93. int InsideCompletion = 0;
  94.  
  95. /* do the expand or list on the command line -- SHOULD BE REPLACED */
  96.  
  97. extern Char NeedsRedraw;    /* from ed.h */
  98. extern int Tty_raw_mode;
  99. extern int TermH;        /* from the editor routines */
  100. extern int lbuffed;        /* from sh.print.c */
  101.  
  102. static    void     extract_dir_and_name    __P((Char *, Char *, Char *));
  103. static    Char    *quote_meta        __P((Char *, int, bool));
  104. static    Char    *dollar            __P((Char *, Char *));
  105. static    Char    *tilde            __P((Char *, Char *));
  106. static  int      expand_dir        __P((Char *, Char *, DIR  **, COMMAND));
  107. static    bool     nostat            __P((Char *));
  108. static    Char     filetype        __P((Char *, Char *));
  109. static    int     t_glob            __P((Char ***, int));
  110. static    int     c_glob            __P((Char ***));
  111. static    int     is_prefix        __P((Char *, Char *));
  112. static    int     is_suffix        __P((Char *, Char *));
  113. static    int     recognize        __P((Char *, Char *, int, int));
  114. static    int     ignored        __P((Char *));
  115. static    int     isadirectory        __P((Char *, Char *));
  116. static  int      tw_collect_items    __P((COMMAND, int, Char *, Char *, 
  117.                          Char *, Char *, int));
  118. static  int      tw_collect        __P((COMMAND, int, Char *, Char *, 
  119.                          Char *, Char *, int, DIR *));
  120. static    Char      tw_suffix        __P((int, Char *, Char *, Char *, 
  121.                          Char *));
  122. static    void      tw_fixword        __P((int, Char *, Char *, Char *, int));
  123. static    void     tw_list_items        __P((int, int, int));
  124.  
  125. #ifdef notdef
  126. /*
  127.  * If we find a set command, then we break a=b to a= and word becomes
  128.  * b else, we don't break a=b. [don't use that; splits words badly and
  129.  * messes up tw_complete()]
  130.  */
  131. #define isaset(c, w) ((w)[-1] == '=' && \
  132.               ((c)[0] == 's' && (c)[1] == 'e' && (c)[2] == 't' && \
  133.                ((c[3] == ' ' || (c)[3] == '\t'))))
  134. #endif
  135.  
  136. #define QLINESIZE (INBUFSIZE + 1)
  137.  
  138. /* tenematch():
  139.  *    Return:
  140.  *        > 1:    No. of items found
  141.  *        = 1:    Exactly one match / spelling corrected
  142.  *        = 0:    No match / spelling was correct
  143.  *        < 0:    Error (incl spelling correction impossible)
  144.  */
  145. int
  146. tenematch(inputline, num_read, command)
  147.     Char   *inputline;        /* match string prefix */
  148.     int     num_read;        /* # actually in inputline */
  149.     COMMAND command;        /* LIST or RECOGNIZE or PRINT_HELP */
  150.  
  151. {
  152.     Char    qline[QLINESIZE];
  153.     Char    qu = 0, *pat = STRNULL;
  154.     Char   *str_end, *cp, *wp, *wordp;
  155.     Char   *cmd_start, *word_start, *word;
  156.     Char   *ocmd_start = NULL, *oword_start = NULL, *oword = NULL;
  157.     int        suf = 0;
  158.     int     space_left;
  159.     int     looking;        /* what we are looking for        */
  160.     int     search_ret;        /* what search returned for debugging     */
  161.     int     backq = 0;
  162.  
  163.     if (num_read > QLINESIZE - 1)
  164.     return -1;
  165.     str_end = &inputline[num_read];
  166.  
  167.     word_start = inputline;
  168.     word = cmd_start = wp = qline;
  169.     for (cp = inputline; cp < str_end; cp++) {
  170.         if (!cmap(qu, _ESC)) {
  171.         if (cmap(*cp, _Q|_ESC)) {
  172.         if (qu == 0 || qu == *cp) {
  173.             qu ^= *cp;
  174.             continue;
  175.         }
  176.         }
  177.         if (qu != '\'' && cmap(*cp, _Q1)) {
  178.         if (backq ^= 1) {
  179.             ocmd_start = cmd_start;
  180.             oword_start = word_start;
  181.             oword = word;
  182.             word_start = cp + 1;
  183.             word = cmd_start = wp + 1;
  184.         }
  185.         else {
  186.             cmd_start = ocmd_start;
  187.             word_start = oword_start;
  188.             word = oword;
  189.         }
  190.         *wp++ = *cp;
  191.         continue;
  192.         }
  193.     }
  194.     if (iscmdmeta(*cp))
  195.         cmd_start = wp + 1;
  196.     /* Don't quote '/' to make the recognize stuff work easily */
  197.     /* Don't quote '$' in double quotes */
  198.     *wp = *cp | (qu && *cp != '/'  && (*cp != '$' || qu != '"') ? 
  199.              QUOTE : 0);
  200.     if (ismetahash(*wp) /* || isaset(cmd_start, wp + 1) */)
  201.         word = wp + 1, word_start = cp + 1;
  202.     wp++;
  203.     if (cmap(qu, _ESC))
  204.         qu = 0;
  205.       }
  206.     *wp = 0;
  207.  
  208.     /* move the word_start after the quote */
  209.     if (*word_start == qu)
  210.     word_start++;
  211.  
  212. #ifdef masscomp
  213.     /*
  214.      * Avoid a nasty message from the RTU 4.1A & RTU 5.0 compiler concerning
  215.      * the "overuse of registers". According to the compiler release notes,
  216.      * incorrect code may be produced unless the offending expression is
  217.      * rewritten. Therefore, we can't just ignore it, DAS DEC-90.
  218.      */
  219.     space_left = QLINESIZE - 1;
  220.     space_left -= word - qline;
  221. #else
  222.     space_left = QLINESIZE - 1 - (word - qline);
  223. #endif
  224.  
  225.     looking = starting_a_command(word - 1, qline) ? 
  226.     TW_COMMAND : TW_ZERO;
  227.     wordp = word;
  228.  
  229. #ifdef TDEBUG
  230.     xprintf("starting_a_command %d\n", looking);
  231.     xprintf("\ncmd_start:%S:\n", cmd_start);
  232.     xprintf("qline:%S:\n", qline);
  233.     xprintf("qline:");
  234.     for (wp = qline; *wp; wp++)
  235.     xprintf("%c", *wp & QUOTE ? '-' : ' ');
  236.     xprintf(":\n");
  237.     xprintf("word:%S:\n", word);
  238.     xprintf("word:");
  239.     /* Must be last, so wp is still pointing to the end of word */
  240.     for (wp = word; *wp; wp++)
  241.     xprintf("%c", *wp & QUOTE ? '-' : ' ');
  242.     xprintf(":\n");
  243. #endif
  244.  
  245.     if (command == RECOGNIZE || command == LIST || command == SPELL) {
  246. #ifdef TDEBUG
  247.     xprintf("complete %d ", looking);
  248. #endif
  249.     looking = tw_complete(cmd_start, &wordp, &pat, looking, &suf);
  250. #ifdef TDEBUG
  251.     xprintf("complete %d %S\n", looking, pat);
  252. #endif
  253.     }
  254.  
  255.     switch ((int) command) {
  256.     Char    buffer[FILSIZ + 1], *bptr;
  257.     Char   *slshp;
  258.     Char   *items[2], **ptr;
  259.     int     i, count;
  260.  
  261.     case RECOGNIZE:
  262.     case RECOGNIZE_ALL:
  263.     if (adrof(STRautocorrect)) {
  264.         if ((slshp = Strrchr(wordp, '/')) != NULL && slshp[1] != '\0') {
  265.         SearchNoDirErr = 1;
  266.         for (bptr = wordp; bptr < slshp; bptr++) {
  267.             /*
  268.              * do not try to correct spelling of words containing
  269.              * globbing characters
  270.              */
  271.             if (isglob(*bptr)) {
  272.             SearchNoDirErr = 0;
  273.             break;
  274.             }
  275.         }
  276.         }
  277.     }
  278.     else
  279.         slshp = STRNULL;
  280.     search_ret = t_search(wordp, wp, RECOGNIZE, space_left, looking, 1, 
  281.                   pat, suf);
  282.     SearchNoDirErr = 0;
  283.  
  284.     if (search_ret == -2) {
  285.         Char    rword[FILSIZ + 1];
  286.  
  287.         (void) Strcpy(rword, slshp);
  288.         if (slshp != STRNULL)
  289.         *slshp = '\0';
  290.         search_ret = spell_me(wordp, QLINESIZE - (wordp - qline), looking);
  291.         if (search_ret == 1) {
  292.         /* get rid of old word */
  293.         DeleteBack(str_end - word_start);
  294.         (void) Strcat(wordp, rword);
  295.         /* insert newly spelled word */
  296.         if (InsertStr(quote_meta(wordp, qu, 0)) < 0)    
  297.             return -1;    /* error inserting */
  298.         wp = wordp + Strlen(wordp);
  299.         search_ret = t_search(wordp, wp, RECOGNIZE, space_left,
  300.                       looking, 1, pat, suf);
  301.         }
  302.     }
  303.  
  304.     /*
  305.      * Change by Christos Zoulas: if the name has metachars in it, quote
  306.      * the metachars, but only if we are outside quotes.
  307.      * We don't quote the last space if we had a unique match and 
  308.      * addsuffix was set. Otherwise the last space was part of a word.
  309.      */
  310.     if (*wp && InsertStr(quote_meta(wp, qu, search_ret == 1 &&
  311.                          (bool) is_set(STRaddsuffix))) < 0)
  312.         /* put it in the input buffer */
  313.         return -1;        /* error inserting */
  314.     return search_ret;
  315.  
  316.     case SPELL:
  317.     for (bptr = word_start; bptr < str_end; bptr++) {
  318.         /*
  319.          * do not try to correct spelling of words containing globbing
  320.          * characters
  321.          */
  322.         if (isglob(*bptr))
  323.         return 0;
  324.     }
  325.     search_ret = spell_me(wordp, QLINESIZE - (wordp - qline), looking);
  326.     if (search_ret == 1) {
  327.         /* get rid of old word */
  328.         DeleteBack(str_end - word_start);    
  329.         /* insert newly spelled word */
  330. #ifdef notdef
  331.         /* 
  332.          * We don't want to quote spelling stuff, otherwise
  333.          * $OHME -> \$HOME 
  334.          */
  335.         if (InsertStr(quote_meta(wordp, qu, 0)) < 0)
  336.         return -1;    /* error inserting */
  337. #endif
  338.         if (InsertStr(wordp) < 0)
  339.         return -1;    /* error inserting */
  340.     }
  341.     return search_ret;
  342.  
  343.     case PRINT_HELP:
  344.     do_help(cmd_start);
  345.     return 1;
  346.  
  347.     case GLOB:
  348.     case GLOB_EXPAND:
  349.     (void) Strncpy(buffer, wordp, FILSIZ + 1);
  350.     items[0] = buffer;
  351.     items[1] = NULL;
  352.     ptr = items;
  353.     count = (looking == TW_COMMAND && Strchr(wordp, '/') == 0) ? 
  354.         c_glob(&ptr) : 
  355.         t_glob(&ptr, looking == TW_COMMAND);
  356.     if (count > 0) {
  357.         if (command == GLOB)
  358.         print_by_column(STRNULL, ptr, count, 0);
  359.         else {
  360.         DeleteBack(str_end - word_start);/* get rid of old word */
  361.         for (i = 0; i < count; i++)
  362.             if (ptr[i] && *ptr[i]) {
  363.             if (InsertStr(quote_meta(ptr[i], qu, 0)) < 0 ||
  364.                 InsertStr(STRspace) < 0) {
  365.                 blkfree(ptr);
  366.                 return (-1);
  367.             }
  368.             }
  369.         }
  370.         blkfree(ptr);
  371.     }
  372.     return count;
  373.  
  374.     case VARS_EXPAND:
  375.     if (dollar(buffer, wordp)) {
  376.         DeleteBack(str_end - word_start);
  377.         if (InsertStr(quote_meta(buffer, qu, 0)) < 0)
  378.         return (-1);
  379.         return (1);
  380.     }
  381.     return (0);
  382.  
  383.     case PATH_NORMALIZE:
  384.     if ((bptr = dnormalize(wordp, symlinks == SYM_IGNORE ||
  385.                       symlinks == SYM_EXPAND)) != NULL) {
  386.         (void) Strcpy(buffer, bptr);
  387.         xfree((ptr_t) bptr);
  388.         DeleteBack(str_end - word_start);
  389.         if (InsertStr(quote_meta(buffer, qu, 0)) < 0)
  390.         return (-1);
  391.         return (1);
  392.     }
  393.     return (0);
  394.  
  395.     case LIST:
  396.     case LIST_ALL:
  397.     search_ret = t_search(wordp, wp, LIST, space_left, looking, 1, 
  398.                   pat, suf);
  399.     return search_ret;
  400.  
  401.     default:
  402.     xprintf("tcsh: Internal match error.\n");
  403.     return 1;
  404.  
  405.     }
  406. } /* end tenematch */
  407.  
  408.  
  409. /* t_glob():
  410.  *     Return a list of files that match the pattern
  411.  */
  412. static int
  413. t_glob(v, cmd)
  414.     register Char ***v;
  415.     int cmd;
  416. {
  417.     jmp_buf_t osetexit;
  418.  
  419.     if (**v == 0)
  420.     return (0);
  421.     gflag = 0, tglob(*v);
  422.     if (gflag) {
  423.     getexit(osetexit);    /* make sure to come back here */
  424.     if (setexit() == 0)
  425.         *v = globall(*v);
  426.     resexit(osetexit);
  427.     gargv = 0;
  428.     if (haderr) {
  429.         haderr = 0;
  430.         NeedsRedraw = 1;
  431.         return (-1);
  432.     }
  433.     if (*v == 0)
  434.         return (0);
  435.     }
  436.     else
  437.     return (0);
  438.  
  439.     if (cmd) {
  440.     Char **av = *v, *p;
  441.     int fwd, i, ac = gargc;
  442.  
  443.     for (i = 0, fwd = 0; i < ac; i++) 
  444.         if (!executable(NULL, av[i], 0)) {
  445.         fwd++;        
  446.         p = av[i];
  447.         av[i] = NULL;
  448.         xfree((ptr_t) p);
  449.         }
  450.         else if (fwd) 
  451.         av[i - fwd] = av[i];
  452.  
  453.     if (fwd)
  454.         av[i - fwd] = av[i];
  455.     gargc -= fwd;
  456.     av[gargc] = NULL;
  457.     }
  458.  
  459.     return (gargc);
  460. } /* end t_glob */
  461.  
  462.  
  463. /* c_glob():
  464.  *     Return a list of commands that match the pattern
  465.  */
  466. static int
  467. c_glob(v)
  468.     register Char ***v;
  469. {
  470.     Char *pat = **v, *cmd, **av;
  471.     Char dir[MAXPATHLEN+1];
  472.     int flag, at, ac;
  473.  
  474.     if (pat == NULL)
  475.     return (0);
  476.  
  477.     ac = 0;
  478.     at = 10;
  479.     av = (Char **) xmalloc((size_t) (at * sizeof(Char *)));
  480.     av[ac] = NULL;
  481.  
  482.     tw_cmd_start(NULL, NULL);
  483.     while ((cmd = tw_cmd_next(dir, &flag)) != NULL) 
  484.     if (Gmatch(cmd, pat)) {
  485.         if (ac + 1 >= at) {
  486.         at += 10;
  487.         av = (Char **) xrealloc((ptr_t) av, 
  488.                     (size_t) (at * sizeof(Char *)));
  489.         }
  490.         av[ac++] = Strsave(cmd);
  491.         av[ac] = NULL;
  492.     }
  493.     tw_dir_end();
  494.     *v = av;
  495.  
  496.     return (ac);
  497. } /* end c_glob */
  498.  
  499.  
  500. /* quote_meta():
  501.  *    quote (\) the meta-characters in a word
  502.  *    except trailing space if trail_space is set
  503.  *    return pointer to quoted word in static storage
  504.  */
  505. static Char *
  506. quote_meta(word, qu, trail_space)
  507.     Char   *word;
  508.     int     qu;
  509.     bool    trail_space;
  510. {
  511.     static Char buffer[2 * FILSIZ + 1], *bptr, *wptr;
  512.  
  513.     for (bptr = buffer, wptr = word; *wptr != '\0';) {
  514.     if (bptr > buffer + 2 * FILSIZ - 4)
  515.         break;
  516.       
  517.     if (qu && *wptr == qu) {
  518.         *bptr++ = qu;
  519.         *bptr++ = '\\';
  520.         *bptr++ = qu;
  521.     }
  522.     if (!qu &&
  523.         (cmap(*wptr, _META | _DOL | _Q | _Q1 | _ESC | _GLOB) ||
  524.          *wptr == HIST || *wptr == HISTSUB) &&
  525.         (*wptr != ' ' || !trail_space || *(wptr + 1) != '\0') &&
  526.              *wptr != '#')
  527.         *bptr++ = '\\';
  528.     *bptr++ = *wptr++;
  529.     if (cmap(qu, _ESC))
  530.       qu = 0;
  531.     }
  532.     *bptr = '\0';
  533.     return (buffer);
  534. } /* end quote_meta */
  535.  
  536.  
  537.  
  538. /* is_prefix():
  539.  *    return true if check matches initial chars in template
  540.  *    This differs from PWB imatch in that if check is null
  541.  *    it matches anything
  542.  */
  543. static int
  544. is_prefix(check, template)
  545.     register Char *check, *template;
  546. {
  547.     for (; *check; check++, template++)
  548.     if ((*check & TRIM) != (*template & TRIM))
  549.         return (FALSE);
  550.     return (TRUE);
  551. } /* end is_prefix */
  552.  
  553.  
  554. /* is_suffix():
  555.  *    Return true if the chars in template appear at the
  556.  *    end of check, I.e., are it's suffix.
  557.  */
  558. static int
  559. is_suffix(check, template)
  560.     register Char *check, *template;
  561. {
  562.     register Char *t, *c;
  563.  
  564.     for (t = template; *t++;)
  565.     continue;
  566.     for (c = check; *c++;)
  567.     continue;
  568.     for (;;) {
  569.     if (t == template)
  570.         return 1;
  571.     --t;
  572.     --c;
  573.     if (c == check || (*t & TRIM) != (*c & TRIM))
  574.         return 0;
  575.     }
  576. } /* end is_suffix */
  577.  
  578.  
  579. /* ignored():
  580.  *    Return true if this is an ignored entry
  581.  */
  582. static int
  583. ignored(entry)
  584.     register Char *entry;
  585. {
  586.     struct varent *vp;
  587.     register Char **cp;
  588.  
  589.     if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL)
  590.     return (FALSE);
  591.     for (; *cp != NULL; cp++)
  592.     if (is_suffix(entry, *cp))
  593.         return (TRUE);
  594.     return (FALSE);
  595. } /* end ignored */
  596.  
  597.  
  598.  
  599. /* starting_a_command():
  600.  *    return true if the command starting at wordstart is a command
  601.  */
  602. int
  603. starting_a_command(wordstart, inputline)
  604.     register Char *wordstart, *inputline;
  605. {
  606.     register Char *ptr, *ncmdstart;
  607.     int     count;
  608.     static  Char
  609.             cmdstart[] = {'`', ';', '&', '(', '|', '\0'},
  610.             cmdalive[] = {' ', '\t', '\'', '"', '<', '>', '\0'};
  611.  
  612.     /*
  613.      * Find if the number of backquotes is odd or even.
  614.      */
  615.     for (ptr = wordstart, count = 0;
  616.      ptr >= inputline;
  617.      count += (*ptr-- == '`'))
  618.     continue;
  619.     /*
  620.      * if the number of backquotes is even don't include the backquote char in
  621.      * the list of command starting delimiters [if it is zero, then it does not
  622.      * matter]
  623.      */
  624.     ncmdstart = cmdstart + EVEN(count);
  625.  
  626.     /*
  627.      * look for the characters previous to this word if we find a command
  628.      * starting delimiter we break. if we find whitespace and another previous
  629.      * word then we are not a command
  630.      * 
  631.      * count is our state machine: 0 looking for anything 1 found white-space
  632.      * looking for non-ws
  633.      */
  634.     for (count = 0; wordstart >= inputline; wordstart--) {
  635.     if (*wordstart == '\0')
  636.         continue;
  637.     if (Strchr(ncmdstart, *wordstart))
  638.         break;
  639.     /*
  640.      * found white space
  641.      */
  642.     if ((ptr = Strchr(cmdalive, *wordstart)) != NULL)
  643.         count = 1;
  644.     if (count == 1 && !ptr)
  645.         return (FALSE);
  646.     }
  647.  
  648.     if (wordstart > inputline)
  649.     switch (*wordstart) {
  650.     case '&':        /* Look for >& */
  651.         while (wordstart > inputline &&
  652.            (*--wordstart == ' ' || *wordstart == '\t'))
  653.         continue;
  654.         if (*wordstart == '>')
  655.         return (FALSE);
  656.         break;
  657.     case '(':        /* check for foreach, if etc. */
  658.         while (wordstart > inputline &&
  659.            (*--wordstart == ' ' || *wordstart == '\t'))
  660.         continue;
  661.         if (!iscmdmeta(*wordstart) &&
  662.         (*wordstart != ' ' && *wordstart != '\t'))
  663.         return (FALSE);
  664.         break;
  665.     default:
  666.         break;
  667.     }
  668.     return (TRUE);
  669. } /* end starting_a_command */
  670.  
  671.  
  672. /* recognize():
  673.  *    Object: extend what user typed up to an ambiguity.
  674.  *    Algorithm:
  675.  *    On first match, copy full entry (assume it'll be the only match)
  676.  *    On subsequent matches, shorten exp_name to the first
  677.  *    character mismatch between exp_name and entry.
  678.  *    If we shorten it back to the prefix length, stop searching.
  679.  */
  680. static int
  681. recognize(exp_name, entry, name_length, numitems)
  682.     Char   *exp_name, *entry;
  683.     int     name_length, numitems;
  684. {
  685.     if (numitems == 1)        /* 1st match */
  686.     copyn(exp_name, entry, MAXNAMLEN);
  687.     else {            /* 2nd and subsequent matches */
  688.     register Char *x, *ent;
  689.     register int len = 0;
  690.  
  691.     for (x = exp_name, ent = entry;
  692.          *x && (*x & TRIM) == (*ent & TRIM); x++, len++, ent++)
  693.         continue;
  694.     *x = '\0';        /* Shorten at 1st char diff */
  695.     if (len == name_length)    /* Ambiguous to prefix? */
  696.         return (-1);    /* So stop now and save time */
  697.     }
  698.     return (0);
  699. } /* end recognize */
  700.  
  701.  
  702. /* tw_collect_items():
  703.  *    Collect items that match target.
  704.  *    SPELL command:
  705.  *        Returns the spelling distance of the closest match.
  706.  *    else
  707.  *        Returns the number of items found.
  708.  *        If none found, but some ignored items were found,
  709.  *        It returns the -number of ignored items.
  710.  */
  711. static int
  712. tw_collect_items(command, looking, exp_dir, exp_name, target, pat, flags)
  713.     COMMAND command;
  714.     int looking;
  715.     Char *exp_dir, *exp_name, *target, *pat;
  716.     int flags;
  717.  
  718. {
  719.     int done = FALSE;             /* Search is done */
  720.     int showdots;             /* Style to show dot files */
  721.     int nignored = 0;             /* Number of fignored items */
  722.     int numitems = 0;             /* Number of matched items */
  723.     int    name_length = Strlen(target);     /* Length of prefix (file name) */
  724.     int exec_check = flags & TW_EXEC_CHK;/* need to check executability    */
  725.     int dir_check  = flags & TW_DIR_CHK; /* Need to check for directories */
  726.     int text_check = flags & TW_TEXT_CHK;/* Need to check for non-directories */
  727.     int dir_ok     = flags & TW_DIR_OK;  /* Ignore directories? */
  728.     int gpat       = flags & TW_PAT_OK;     /* Match against a pattern */
  729.     int ignoring   = flags & TW_IGN_OK;     /* Use fignore? */
  730.     int d = 4, nd;             /* Spelling distance */
  731.     Char *entry, *ptr;
  732.     Char buf[MAXPATHLEN+1];
  733.     struct varent *vp;
  734.     int len;
  735.     flags = 0;
  736.  
  737.     if ((vp = adrof(STRshowdots)) != NULL) {
  738.     if (vp->vec[0][0] == '-' && vp->vec[0][1] == 'A' &&
  739.         vp->vec[0][2] == '\0')
  740.         showdots = DOT_NOT;
  741.     else
  742.         showdots = DOT_ALL;
  743.     }
  744.     else
  745.     showdots = DOT_NONE;
  746.  
  747.     while (!done && (entry = (*tw_next_entry[looking])(exp_dir, &flags))) {
  748. #ifdef TDEBUG
  749.     xprintf("entry = %S\n", entry);
  750. #endif
  751.     switch (looking) {
  752.     case TW_FILE:
  753.     case TW_DIRECTORY:
  754.     case TW_TEXT:
  755.         /*
  756.          * Don't match . files on null prefix match
  757.          */
  758.         if (showdots == DOT_NOT && (ISDOT(entry) || ISDOTDOT(entry)))
  759.         done = TRUE;
  760.         if (name_length == 0 && entry[0] == '.' && showdots == DOT_NONE)
  761.         done = TRUE;
  762.         break;
  763.  
  764.     case TW_COMMAND:
  765.         exec_check = flags & TW_EXEC_CHK;
  766.         dir_ok = flags & TW_DIR_OK;
  767.         break;
  768.  
  769.     default:
  770.         break;
  771.     }
  772.  
  773.     if (done) {
  774.         done = FALSE;
  775.         continue;
  776.     }
  777.  
  778.     switch (command) {
  779.  
  780.     case SPELL:        /* correct the spelling of the last bit */
  781.         if (name_length == 0) {/* zero-length word can't be misspelled */
  782.         exp_name[0] = '\0';/* (not trying is important for ~) */
  783.         d = 0;
  784.         done = TRUE;
  785.         break;
  786.         }
  787.         if (gpat && !Gmatch(entry, pat))
  788.         break;
  789.         nd = spdist(entry, target);    /* test the entry against original */
  790.         if (nd <= d && nd != 4) {
  791.         if (!(exec_check && !executable(exp_dir, entry, dir_ok))) {
  792.             (void) Strcpy(exp_name, entry);
  793.             d = nd;
  794.             if (d == 0)    /* if found it exactly */
  795.             done = TRUE;
  796.         }
  797.         }
  798.         else if (nd == 4) {
  799.         if (spdir(exp_name, exp_dir, entry, target)) {
  800.             if (exec_check && !executable(exp_dir, exp_name, dir_ok)) 
  801.             break;
  802. #ifdef notdef
  803.             /*
  804.              * We don't want to stop immediately, because
  805.              * we might find an exact/better match later.
  806.              */
  807.             d = 0;
  808.             done = TRUE;
  809. #endif
  810.             d = 3;
  811.         }
  812.         }
  813.         break;
  814.  
  815.     case LIST:
  816.     case RECOGNIZE:
  817.  
  818.         if (!is_prefix(target, entry)) 
  819.         break;
  820.  
  821.         if (exec_check && !executable(exp_dir, entry, dir_ok))
  822.         break;
  823.  
  824.         if (dir_check && !isadirectory(exp_dir, entry))
  825.         break;
  826.         
  827.         if (text_check && isadirectory(exp_dir, entry))
  828.         break;
  829.  
  830.         if (gpat && !Gmatch(entry, pat) && !isadirectory(exp_dir, entry))
  831.         break;
  832.  
  833.         /*
  834.          * Remove duplicates in command listing and completion
  835.          */
  836.         if (looking == TW_COMMAND || command == LIST) {
  837.         copyn(buf, entry, MAXPATHLEN);
  838.         len = Strlen(buf);
  839.         switch (looking) {
  840.         case TW_COMMAND:
  841.             if (!(dir_ok && exec_check))
  842.             break;
  843.             if (filetype(exp_dir, entry) == '/') {
  844.             buf[len++] = '/';
  845.             buf[len] = '\0';
  846.             }
  847.             break;
  848.  
  849.         case TW_FILE:
  850.         case TW_DIRECTORY:
  851.             buf[len++] = filetype(exp_dir, entry);
  852.             buf[len] = '\0';
  853.             break;
  854.  
  855.         default:
  856.             break;
  857.         }
  858.         if (looking == TW_COMMAND && tw_item_find(buf))
  859.             break;
  860.         else {
  861.             /* maximum length 1 (NULL) + 1 (~ or $) + 1 (filetype) */
  862.             ptr = tw_item_add(len + 3);
  863.             copyn(ptr, buf, MAXPATHLEN);
  864.             if (command == LIST)
  865.             numitems++;
  866.         }
  867.         }
  868.             
  869.         if (command == RECOGNIZE) {
  870.         if (ignoring && ignored(entry)) {
  871.             nignored++;
  872.             break;
  873.         }
  874.         if (is_set(STRrecexact)) {
  875.             if (StrQcmp(target, entry) == 0) {    /* EXACT match */
  876.             copyn(exp_name, entry, MAXNAMLEN);
  877.             numitems = 1;    /* fake into expanding */
  878.             non_unique_match = TRUE;
  879.             done = TRUE;
  880.             break;
  881.             }
  882.         }
  883.         if (recognize(exp_name, entry, name_length, ++numitems)) 
  884.             done = TRUE;
  885.         }
  886.         break;
  887.  
  888.     default:
  889.         break;
  890.     }
  891. #ifdef TDEBUG
  892.     xprintf("done entry = %S\n", entry);
  893. #endif
  894.     }
  895.     if (command == SPELL)
  896.     return d;
  897.     else {
  898.     if (ignoring && numitems == 0 && nignored > 0) 
  899.         return -nignored;
  900.     else
  901.         return numitems;
  902.     }
  903. }
  904.  
  905.  
  906. /* tw_suffix():
  907.  *    Find and return the appropriate suffix character
  908.  */
  909. static Char 
  910. tw_suffix(looking, exp_dir, exp_name, target, name)
  911.     int looking;
  912.     Char *exp_dir, *exp_name, *target, *name;
  913. {    
  914.     Char *ptr;
  915.     struct varent *vp;
  916.  
  917.     switch (looking) {
  918.  
  919.     case TW_LOGNAME:
  920.     return '/';
  921.  
  922.     case TW_VARIABLE:
  923.     /*
  924.      * Don't consider array variables or empty variables
  925.      */
  926.     if ((vp = adrof(exp_name)) != NULL) {
  927.         if ((ptr = vp->vec[0]) == NULL || *ptr == '\0' ||
  928.         vp->vec[1] != NULL) 
  929.         return ' ';
  930.     }
  931.     else if ((ptr = tgetenv(exp_name)) == NULL || *ptr == '\0')
  932.         return ' ';
  933.     *--target = '\0';
  934.     (void) Strcat(exp_dir, name);
  935.     return isadirectory(exp_dir, ptr) ? '/' : ' ';
  936.  
  937.  
  938.     case TW_DIRECTORY:
  939.     return '/';
  940.  
  941.     case TW_COMMAND:
  942.     case TW_FILE:
  943.     return isadirectory(exp_dir, exp_name) ? '/' : ' ';
  944.  
  945.     case TW_ALIAS:
  946.     case TW_VARLIST:
  947.     case TW_WORDLIST:
  948.     case TW_SHELLVAR:
  949.     case TW_ENVVAR:
  950.     case TW_USER:
  951.     case TW_BINDING:
  952.     case TW_LIMIT:
  953.     case TW_SIGNAL:
  954.     case TW_JOB:
  955.     case TW_COMPLETION:
  956.     case TW_TEXT:
  957.     return ' ';
  958.  
  959.     default:
  960.     return '\0';
  961.     }
  962. } /* end tw_suffix */
  963.  
  964.  
  965. /* tw_fixword():
  966.  *    Repair a word after a spalling or a recognizwe
  967.  */
  968. static void
  969. tw_fixword(looking, word, dir, exp_name, max_word_length)
  970.     int looking;
  971.     Char *word, *dir, *exp_name;
  972.     int max_word_length;
  973. {
  974.     Char *ptr;
  975.  
  976.     switch (looking) {
  977.     case TW_LOGNAME:
  978.     copyn(word, STRtilde, 1);
  979.     break;
  980.     
  981.     case TW_VARIABLE:
  982.     if ((ptr = Strrchr(word, '$')) != NULL)
  983.         *++ptr = '\0';    /* Delete after the dollar */
  984.     else
  985.         word[0] = '\0';
  986.     break;
  987.  
  988.     case TW_DIRECTORY:
  989.     case TW_FILE:
  990.     case TW_TEXT:
  991.     copyn(word, dir, max_word_length);    /* put back dir part */
  992.     break;
  993.  
  994.     default:
  995.     word[0] = '\0';
  996.     break;
  997.     }
  998.  
  999.     catn(word, exp_name, max_word_length);    /* add extended name */
  1000. } /* end tw_fixword */
  1001.  
  1002.  
  1003. /* tw_collect():
  1004.  *    Collect items. Return -1 in case we were interrupted or
  1005.  *    the return value of tw_collect
  1006.  *    This is really a wrapper for tw_collect_items, serving two
  1007.  *    purposes:
  1008.  *        1. Handles interrupt cleanups.
  1009.  *        2. Retries if we had no matches, but there were ignored matches
  1010.  */
  1011. static int
  1012. tw_collect(command, looking, exp_dir, exp_name, target, pat, flags, dir_fd)
  1013.     COMMAND command;
  1014.     int looking;
  1015.     Char *exp_dir, *exp_name, *target, *pat;
  1016.     int flags;
  1017.     DIR *dir_fd;
  1018. {
  1019.     static int ni;    /* static so we don't get clobbered */
  1020.     jmp_buf_t osetexit;
  1021.  
  1022. #ifdef TDEBUG
  1023.     xprintf("target = %S\n", target);
  1024. #endif
  1025.     ni = 0;
  1026.     getexit(osetexit);
  1027.     for (;;) {
  1028.     (*tw_start_entry[looking])(dir_fd, pat);
  1029.     InsideCompletion = 1;
  1030.     if (setexit()) {
  1031.         /* interrupted, clean up */
  1032.         resexit(osetexit);
  1033.         InsideCompletion = 0;
  1034.         haderr = 0;
  1035.         (*tw_end_entry[looking])();
  1036.         /* flag error */
  1037.         return(-1);
  1038.     }
  1039.         if ((ni = tw_collect_items(command, looking, exp_dir, exp_name,
  1040.                        target, pat, 
  1041.                    ni >= 0 ? flags : 
  1042.                     flags & ~TW_IGN_OK)) >= 0) {
  1043.         resexit(osetexit);
  1044.         InsideCompletion = 0;
  1045.         (*tw_end_entry[looking])();
  1046.         return(ni);
  1047.     }
  1048.     }
  1049. } /* end tw_collect */
  1050.  
  1051.  
  1052. /* tw_list_items():
  1053.  *    List the items that were found
  1054.  */
  1055. static void
  1056. tw_list_items(looking, numitems, list_max)
  1057.     int looking, numitems, list_max;
  1058. {
  1059.     Char *ptr;
  1060.     int max_items = 0;
  1061.  
  1062.     if ((ptr = value(STRlistmax)) != STRNULL) {
  1063.     while (*ptr) {
  1064.         if (!Isdigit(*ptr)) {
  1065.         max_items = 0;
  1066.         break;
  1067.         }
  1068.         max_items = max_items * 10 + *ptr++ - '0';
  1069.     }
  1070.     }
  1071.  
  1072.     if ((max_items > 0) && (numitems > max_items) && list_max) {
  1073.     char    tc;
  1074.  
  1075.     xprintf("There are %d items, list them anyway? [n/y] ", numitems);
  1076.     flush();
  1077.     /* We should be in Rawmode here, so no \n to catch */
  1078.     (void) read(SHIN, &tc, 1);
  1079.     xprintf("%c\r\n", tc);    /* echo the char, do a newline */
  1080.     if ((tc != 'y') && (tc != 'Y'))
  1081.         return;
  1082.     }
  1083.  
  1084.     if (looking != TW_SIGNAL)
  1085.     qsort((ptr_t) tw_item_get(), (size_t) numitems, sizeof(Char *), 
  1086.           (int (*) __P((const void *, const void *))) fcompare);
  1087.     if (looking != TW_JOB)
  1088.     print_by_column(STRNULL, tw_item_get(), numitems, TRUE);
  1089.     else {
  1090.     /*
  1091.      * print one item on every line because jobs can have spaces
  1092.      * and it is confusing.
  1093.      */
  1094.     int i;
  1095.     Char **w = tw_item_get();
  1096.  
  1097.     for (i = 0; i < numitems; i++) {
  1098.         xprintf("%S", w[i]);
  1099.         if (Tty_raw_mode)
  1100.         xputchar('\r');
  1101.         xputchar('\n');
  1102.     }
  1103.     }
  1104. } /* end tw_list_items */
  1105.  
  1106.  
  1107. /* t_search():
  1108.  *    Perform a RECOGNIZE, LIST or SPELL command on string "word".
  1109.  *
  1110.  *    Return value:
  1111.  *        >= 0:   SPELL command: "distance" (see spdist())
  1112.  *                        other: No. of items found
  1113.  *           < 0:   Error (message or beep is output)
  1114.  */
  1115. /*ARGSUSED*/
  1116. int
  1117. t_search(word, wp, command, max_word_length, looking, list_max, pat, suf)
  1118.     Char   *word, *wp;        /* original end-of-word */
  1119.     COMMAND command;
  1120.     int     max_word_length, looking, list_max;
  1121.     Char   *pat;
  1122.     int     suf;
  1123. {
  1124.     int     numitems,            /* Number of items matched */
  1125.         flags = 0,            /* search flags */
  1126.         gpat = pat[0] != '\0',    /* Glob pattern search */
  1127.         nd;                /* Normalized directory return */
  1128.     Char    exp_dir[FILSIZ + 1],    /* dir after ~ expansion */
  1129.             dir[FILSIZ + 1],        /* /x/y/z/ part in /x/y/z/f */
  1130.             exp_name[MAXNAMLEN + 1],    /* the recognized (extended) */
  1131.             name[MAXNAMLEN + 1],    /* f part in /d/d/d/f name */
  1132.            *target;            /* Target to expand/correct/list */
  1133.     DIR    *dir_fd = NULL;    
  1134.  
  1135.     /*
  1136.      * bugfix by Marty Grossman (grossman@CC5.BBN.COM): directory listing can
  1137.      * dump core when interrupted
  1138.      */
  1139.     tw_item_free();
  1140.  
  1141.     non_unique_match = FALSE;    /* See the recexact code below */
  1142.  
  1143.     extract_dir_and_name(word, dir, name);
  1144.     exp_dir[0] = '\0';
  1145.  
  1146.     /*
  1147.      * Try to figure out what we should be looking for
  1148.      */
  1149.  
  1150.     switch (looking) {
  1151.     case TW_NONE:
  1152.     return -1;
  1153.  
  1154.     case TW_ZERO:
  1155.     looking = TW_FILE;
  1156.     break;
  1157.  
  1158.     case TW_COMMAND:
  1159.     if (Strchr(word, '/')) {
  1160.         looking = TW_FILE;
  1161.         flags |= TW_EXEC_CHK;
  1162.         flags |= TW_DIR_OK;
  1163.     }
  1164. #ifdef notdef
  1165.     /* PWP: don't even bother when doing ALL of the commands */
  1166.     if (looking == TW_COMMAND && (*word == '\0')) 
  1167.         return (-1);
  1168. #endif
  1169.     break;
  1170.     case TW_PATHNAME:
  1171.     gpat = 0;    /* pattern holds the pathname to be used */
  1172.     copyn(exp_dir, pat, MAXNAMLEN);
  1173.     catn(exp_dir, dir, MAXNAMLEN);
  1174.     dir[0] = '\0';
  1175.     break;
  1176.  
  1177.     case TW_VARLIST:
  1178.     case TW_WORDLIST:
  1179.     gpat = 0;    /* pattern holds the name of the variable */
  1180.     break;
  1181.  
  1182.     case TW_EXPLAIN:
  1183.     if (command == LIST && pat != NULL) {
  1184.         xprintf("%S", pat);
  1185.         if (Tty_raw_mode)
  1186.         xputchar('\r');
  1187.         xputchar('\n');
  1188.     }
  1189.     return 2;
  1190.  
  1191.     default:
  1192.     break;
  1193.     }
  1194.  
  1195.     /*
  1196.      * let fignore work only when we are not using a pattern
  1197.      */
  1198.     flags |= (gpat == 0) ? TW_IGN_OK : TW_PAT_OK;
  1199.  
  1200.     if ((*word == '~') && (Strchr(word, '/') == NULL)) {
  1201.     looking = TW_LOGNAME;
  1202.     target = name;
  1203.     }
  1204.     else if ((target = Strrchr(name, '$')) != 0 && 
  1205.          (Strchr(name, '/') == NULL)) {
  1206.     target++;
  1207.     looking = TW_VARIABLE;
  1208.     }
  1209.     else
  1210.     target = name;
  1211.  
  1212. #ifdef TDEBUG
  1213.     xprintf("looking = %d\n", looking);
  1214. #endif
  1215.  
  1216.     switch (looking) {
  1217.     case TW_ALIAS:
  1218.     case TW_SHELLVAR:
  1219.     case TW_ENVVAR:
  1220.     case TW_BINDING:
  1221.     case TW_LIMIT:
  1222.     case TW_SIGNAL:
  1223.     case TW_JOB:
  1224.     case TW_COMPLETION:
  1225.     break;
  1226.  
  1227.  
  1228.     case TW_VARIABLE:
  1229.     if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0)
  1230.         return nd;
  1231.     break;
  1232.  
  1233.     case TW_DIRECTORY:
  1234.     flags |= TW_DIR_CHK;
  1235.  
  1236. #ifdef notyet
  1237.     /*
  1238.      * This is supposed to expand the directory stack.
  1239.      * Problems:
  1240.      * 1. Slow
  1241.      * 2. directories with the same name
  1242.      */
  1243.     flags |= TW_DIR_OK;
  1244. #endif
  1245. #ifdef notyet
  1246.     /*
  1247.      * Supposed to do delayed expansion, but it is inconsistent
  1248.      * from a user-interface point of view, since it does not
  1249.      * immediately obey addsuffix
  1250.      */
  1251.     if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0)
  1252.         return nd;
  1253.     if (isadirectory(exp_dir, name)) {
  1254.         if (exp_dir[0] != '\0' || name[0] != '\0') {
  1255.         catn(dir, name, MAXNAMLEN);
  1256.         if (dir[Strlen(dir) - 1] != '/')
  1257.             catn(dir, STRslash, MAXNAMLEN);
  1258.         if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0)
  1259.             return nd;
  1260.         if (word[Strlen(word) - 1] != '/')
  1261.             catn(word, STRslash, MAXNAMLEN);
  1262.         name[0] = '\0';
  1263.         }
  1264.     }
  1265. #endif
  1266.     if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0)
  1267.         return nd;
  1268.     break;
  1269.  
  1270.     case TW_TEXT:
  1271.     flags |= TW_TEXT_CHK;
  1272.     /*FALLTHROUGH*/
  1273.     case TW_FILE:
  1274.     if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0)
  1275.         return nd;
  1276.     break;
  1277.     case TW_PATHNAME:
  1278.     if ((dir_fd = opendir(short2str(exp_dir))) == NULL) {
  1279.         xprintf("%S: %s\n", exp_dir, strerror(errno));
  1280.         return -1;
  1281.     }
  1282.     looking = TW_FILE;
  1283.     break;
  1284.  
  1285.     case TW_LOGNAME:
  1286.     word++;
  1287.     /*FALLTHROUGH*/
  1288.     case TW_USER:
  1289.     /*
  1290.      * Check if the spelling was already correct
  1291.      * From: Rob McMahon <cudcv@cu.warwick.ac.uk>
  1292.      */
  1293.     if (command == SPELL && getpwnam(short2str(word)) != NULL) {
  1294. #ifdef YPBUGS
  1295.         fix_yp_bugs();
  1296. #endif /* YPBUGS */
  1297.         return (0);
  1298.     }
  1299.     copyn(name, word, MAXNAMLEN);    /* name sans ~ */
  1300.     if (looking == TW_LOGNAME)
  1301.         word--;
  1302.     break;
  1303.  
  1304.     case TW_COMMAND:
  1305.     case TW_VARLIST:
  1306.     case TW_WORDLIST:
  1307.     copyn(target, word, MAXNAMLEN);    /* so it can match things */
  1308.     break;
  1309.  
  1310.     default:
  1311.     xprintf("\ntcsh internal error: I don't know what I'm looking for!\n");
  1312.     NeedsRedraw = 1;
  1313.     return (-1);
  1314.     }
  1315.  
  1316.     numitems = tw_collect(command, looking, exp_dir, exp_name, 
  1317.               target, pat, flags, dir_fd);
  1318.     if (numitems == -1)
  1319.     return -1;
  1320.  
  1321.     switch (command) {
  1322.     case RECOGNIZE:
  1323.     if (numitems <= 0) 
  1324.         return (numitems);
  1325.  
  1326.     tw_fixword(looking, word, dir, exp_name, max_word_length);
  1327.  
  1328.     if (is_set(STRaddsuffix) && numitems == 1) {
  1329.         Char suffix[2];
  1330.  
  1331.         suffix[1] = '\0';
  1332.         switch (suf) {
  1333.         case 0:     /* Automatic suffix */
  1334.         suffix[0] = tw_suffix(looking, exp_dir, exp_name, target, name);
  1335.         break;
  1336.  
  1337.         case -1:    /* No suffix */
  1338.         return numitems;
  1339.  
  1340.         default:    /* completion specified suffix */
  1341.         suffix[0] = suf;
  1342.         break;
  1343.         }
  1344.         catn(word, suffix, max_word_length);
  1345.     }
  1346.     return numitems;
  1347.  
  1348.     case LIST:
  1349.     tw_list_items(looking, numitems, list_max);
  1350.     tw_item_free();
  1351.     return (numitems);
  1352.  
  1353.     case SPELL:
  1354.     tw_fixword(looking, word, dir, exp_name, max_word_length);
  1355.     return (numitems);
  1356.  
  1357.     default:
  1358.     xprintf("Bad tw_command\n");
  1359.     return (0);
  1360.     }
  1361. } /* end t_search */
  1362.  
  1363.  
  1364. /* extract_dir_and_name():
  1365.  *     parse full path in file into 2 parts: directory and file names
  1366.  *     Should leave final slash (/) at end of dir.
  1367.  */
  1368. static void
  1369. extract_dir_and_name(path, dir, name)
  1370.     Char   *path, *dir, *name;
  1371. {
  1372.     register Char *p;
  1373.  
  1374.     p = Strrchr(path, '/');
  1375.     if (p == NULL) {
  1376.     copyn(name, path, MAXNAMLEN);
  1377.     dir[0] = '\0';
  1378.     }
  1379.     else {
  1380.     p++;
  1381.     copyn(name, p, MAXNAMLEN);
  1382.     copyn(dir, path, p - path);
  1383.     }
  1384. } /* end extract_dir_and_name */
  1385.  
  1386.  
  1387. /* dollar():
  1388.  *     expand "/$old1/$old2/old3/"
  1389.  *     to "/value_of_old1/value_of_old2/old3/"
  1390.  */
  1391. static Char *
  1392. dollar(new, old)
  1393.     Char   *new, *old;
  1394. {
  1395.     Char   *var, *val, *p, save;
  1396.     int     space;
  1397.  
  1398.     for (space = FILSIZ, p = new; *old && space > 0;)
  1399.     if (*old != '$') {
  1400.         *p++ = *old++;
  1401.         space--;
  1402.     }
  1403.     else {
  1404.         struct varent *vp;
  1405.  
  1406.         /* found a variable, expand it */
  1407.         for (var = ++old; alnum(*old); old++)
  1408.         continue;
  1409.         save = *old;
  1410.         *old = '\0';
  1411.         vp = adrof(var);
  1412.         val = (!vp) ? tgetenv(var) : NULL;
  1413.         *old = save;
  1414.         if (vp) {
  1415.         int i;
  1416.         for (i = 0; vp->vec[i] != NULL; i++) {
  1417.             for (val = vp->vec[i]; space > 0 && *val; space--)
  1418.             *p++ = *val++;
  1419.             if (vp->vec[i+1] && space > 0) {
  1420.             *p++ = ' ';
  1421.             space--;
  1422.             }
  1423.         }
  1424.         }
  1425.         else if (val) {
  1426.         for (;space > 0 && *val; space--)
  1427.             *p++ = *val++;
  1428.         }
  1429.         else {
  1430.         *new = '\0';
  1431.         return (NULL);
  1432.         }
  1433.     }
  1434.     *p = '\0';
  1435.     return (new);
  1436. } /* end dollar */
  1437.  
  1438.  
  1439. /* tilde():
  1440.  *     expand ~person/foo to home_directory_of_person/foo
  1441.  *    or =<stack-entry> to <dir in stack entry>
  1442.  */
  1443. static Char *
  1444. tilde(new, old)
  1445.     Char   *new, *old;
  1446. {
  1447.     register Char *o, *p;
  1448.  
  1449.     switch (old[0]) {
  1450.     case '~':
  1451.     for (p = new, o = &old[1]; *o && *o != '/'; *p++ = *o++) 
  1452.         continue;
  1453.     *p = '\0';
  1454.     if (gethdir(new)) {
  1455.         new[0] = '\0';
  1456.         return NULL;
  1457.     }
  1458.     (void) Strcat(new, o);
  1459.     return new;
  1460.  
  1461.     case '=':
  1462.     if ((p = globequal(new, old)) == NULL) {
  1463.         *new = '\0';
  1464.         return NULL;
  1465.     }
  1466.     if (p == new)
  1467.         return new;
  1468.     /*FALLTHROUGH*/
  1469.  
  1470.     default:
  1471.     (void) Strcpy(new, old);
  1472.     return new;
  1473.     }
  1474. } /* end tilde */
  1475.  
  1476.  
  1477. /* expand_dir():
  1478.  *    Open the directory given, expanding ~user and $var
  1479.  *    Optionally normalize the path given
  1480.  */
  1481. static int
  1482. expand_dir(dir, edir, dfd, cmd)
  1483.     Char   *dir, *edir;
  1484.     DIR   **dfd;
  1485.     COMMAND cmd;
  1486. {
  1487.     Char   *nd = NULL;
  1488.     Char    tdir[MAXPATHLEN + 1];
  1489.  
  1490.     if ((dollar(tdir, dir) == 0) ||
  1491.     (tilde(edir, tdir) == 0) ||
  1492.     !(nd = dnormalize(*edir ? edir : STRdot, symlinks == SYM_IGNORE ||
  1493.                          symlinks == SYM_EXPAND)) ||
  1494.     ((*dfd = opendir(short2str(nd))) == NULL)) {
  1495.     xfree((ptr_t) nd);
  1496.     if (cmd == SPELL || SearchNoDirErr)
  1497.         return (-2);
  1498.     /*
  1499.      * From: Amos Shapira <amoss@cs.huji.ac.il>
  1500.      * Print a better message when completion fails
  1501.      */
  1502.     xprintf("\n%S %s\n",
  1503.         *edir ? edir :
  1504.         (*tdir ? tdir : dir),
  1505.         (errno == ENOTDIR ? "not a directory" :
  1506.         (errno == ENOENT ? "not found" : "unreadable")));
  1507.     NeedsRedraw = 1;
  1508.     return (-1);
  1509.     }
  1510.     if (nd) {
  1511.     if (*dir != '\0') {
  1512.         Char   *s, *d, *p;
  1513.  
  1514.         /*
  1515.          * Copy and append a / if there was one
  1516.          */
  1517.         for (p = edir; *p; p++)
  1518.         continue;
  1519.         if (*--p == '/') {
  1520.         for (p = nd; *p; p++)
  1521.             continue;
  1522.         if (*--p != '/')
  1523.             p = NULL;
  1524.         }
  1525.         for (d = edir, s = nd; (*d++ = *s++) != '\0';)
  1526.         continue;
  1527.         if (!p) {
  1528.         *d-- = '\0';
  1529.         *d = '/';
  1530.         }
  1531.     }
  1532.     xfree((ptr_t) nd);
  1533.     }
  1534.     return 0;
  1535. } /* end expand_dir */
  1536.  
  1537.  
  1538. /* nostat():
  1539.  *    Returns true if the directory should not be stat'd,
  1540.  *    false otherwise.
  1541.  *    This way, things won't grind to a halt when you complete in /afs
  1542.  *    or very large directories.
  1543.  */
  1544. static bool
  1545. nostat(dir)
  1546.      Char *dir;
  1547. {
  1548.     struct varent *vp;
  1549.     register Char **cp;
  1550.  
  1551.     if ((vp = adrof(STRnostat)) == NULL || (cp = vp->vec) == NULL)
  1552.     return FALSE;
  1553.     for (; *cp != NULL; cp++) {
  1554.     if (Strcmp(*cp, STRstar) == 0)
  1555.         return TRUE;
  1556.     if (Gmatch(dir, *cp))
  1557.         return TRUE;
  1558.     }
  1559.     return FALSE;
  1560. } /* end nostat */
  1561.  
  1562.  
  1563. /* filetype():
  1564.  *    Return a character that signifies a filetype
  1565.  *    symbology from 4.3 ls command.
  1566.  */
  1567. static  Char
  1568. filetype(dir, file)
  1569.     Char   *dir, *file;
  1570. {
  1571.     if (dir) {
  1572.     Char    path[512];
  1573.     char   *ptr;
  1574.     struct stat statb;
  1575.  
  1576.     if (nostat(dir)) return(' ');
  1577.  
  1578.     (void) Strcpy(path, dir);
  1579.     catn(path, file, sizeof(path) / sizeof(Char));
  1580.  
  1581.     if (lstat(ptr = short2str(path), &statb) != -1)
  1582.         /* see above #define of lstat */
  1583.     {
  1584. #ifdef S_ISLNK
  1585.         if (S_ISLNK(statb.st_mode)) {    /* Symbolic link */
  1586.         if (adrof(STRlistlinks)) {
  1587.             if (stat(ptr, &statb) == -1)
  1588.             return ('&');
  1589.             else if (S_ISDIR(statb.st_mode))
  1590.             return ('>');
  1591.             else
  1592.             return ('@');
  1593.         }
  1594.         else
  1595.             return ('@');
  1596.         }
  1597. #endif
  1598. #ifdef S_ISSOCK
  1599.         if (S_ISSOCK(statb.st_mode))    /* Socket */
  1600.         return ('=');
  1601. #endif
  1602. #ifdef S_ISFIFO
  1603.         if (S_ISFIFO(statb.st_mode)) /* Named Pipe */
  1604.         return ('|');
  1605. #endif
  1606. #ifdef S_ISHIDDEN
  1607.         if (S_ISHIDDEN(statb.st_mode)) /* Hidden Directory [aix] */
  1608.         return ('+');
  1609. #endif
  1610. #ifdef S_ISCDF    
  1611.         if (S_ISCDF(statb.st_mode))    /* Context Dependent Files [hpux] */
  1612.         return ('+');
  1613. #endif 
  1614. #ifdef S_ISNWK
  1615.         if (S_ISNWK(statb.st_mode)) /* Network Special [hpux] */
  1616.         return (':');
  1617. #endif
  1618. #ifdef S_ISCHR
  1619.         if (S_ISCHR(statb.st_mode))    /* char device */
  1620.         return ('%');
  1621. #endif
  1622. #ifdef S_ISBLK
  1623.         if (S_ISBLK(statb.st_mode))    /* block device */
  1624.         return ('#');
  1625. #endif
  1626. #ifdef S_ISDIR
  1627.         if (S_ISDIR(statb.st_mode))    /* normal Directory */
  1628.         return ('/');
  1629. #endif
  1630.         if (statb.st_mode & 0111)
  1631.         return ('*');
  1632.     }
  1633.     }
  1634.     return (' ');
  1635. } /* end filetype */
  1636.  
  1637.  
  1638. /* isadirectory():
  1639.  *    Return trus if the file is a directory
  1640.  */
  1641. static int
  1642. isadirectory(dir, file)        /* return 1 if dir/file is a directory */
  1643.     Char   *dir, *file;        /* uses stat rather than lstat to get dest. */
  1644. {
  1645.     if (dir) {
  1646.     Char    path[MAXPATHLEN];
  1647.     struct stat statb;
  1648.  
  1649.     (void) Strcpy(path, dir);
  1650.     catn(path, file, sizeof(path) / sizeof(Char));
  1651.     if (stat(short2str(path), &statb) >= 0) {    /* resolve through
  1652.                              * symlink */
  1653. #ifdef S_ISSOCK
  1654.         if (S_ISSOCK(statb.st_mode))    /* Socket */
  1655.         return 0;
  1656. #endif
  1657. #ifdef S_ISFIFO
  1658.         if (S_ISFIFO(statb.st_mode))    /* Named Pipe */
  1659.         return 0;
  1660. #endif
  1661.         if (S_ISDIR(statb.st_mode))    /* normal Directory */
  1662.         return 1;
  1663.     }
  1664.     }
  1665.     return 0;
  1666. } /* end isadirectory */
  1667.  
  1668.  
  1669. /* print_by_column():
  1670.  *     Print sorted down columns
  1671.  */
  1672. void
  1673. print_by_column(dir, items, count, no_file_suffix)
  1674.     register Char *dir, *items[];
  1675.     int     count, no_file_suffix;
  1676. {
  1677.     register int i, r, c, columns, rows;
  1678.     unsigned int w, maxwidth = 0;
  1679.  
  1680.     lbuffed = 0;        /* turn off line buffering */
  1681.  
  1682.     for (i = 0; i < count; i++)    /* find widest string */
  1683.     maxwidth = max(maxwidth, Strlen(items[i]));
  1684.  
  1685.     maxwidth += no_file_suffix ? 1 : 2;    /* for the file tag and space */
  1686.     columns = (TermH + 1) / maxwidth;    /* PWP: terminal size change */
  1687.     if (!columns)
  1688.     columns = 1;
  1689.     rows = (count + (columns - 1)) / columns;
  1690.  
  1691.     for (r = 0; r < rows; r++) {
  1692.     for (c = 0; c < columns; c++) {
  1693.         i = c * rows + r;
  1694.  
  1695.         if (i < count) {
  1696.         w = Strlen(items[i]);
  1697.  
  1698.         if (no_file_suffix) {
  1699.             /* Print the command name */
  1700.             xprintf("%S", items[i]);
  1701.         }
  1702.         else {
  1703.             /* Print filename followed by '/' or '*' or ' ' */
  1704.             xprintf("%S%c", items[i],
  1705.                 filetype(dir, items[i]));
  1706.             w++;
  1707.         }
  1708.  
  1709.         if (c < (columns - 1))    /* Not last column? */
  1710.             for (; w < maxwidth; w++)
  1711.             xputchar(' ');
  1712.         }
  1713.     }
  1714.     if (Tty_raw_mode)
  1715.         xputchar('\r');
  1716.     xputchar('\n');
  1717.     }
  1718.  
  1719.     lbuffed = 1;        /* turn back on line buffering */
  1720.     flush();
  1721. } /* end print_by_column */
  1722.  
  1723.  
  1724. /* StrQcmp():
  1725.  *    Compare strings ignoring the quoting chars
  1726.  */
  1727. int
  1728. StrQcmp(str1, str2)
  1729.     register Char *str1, *str2;
  1730. {
  1731.     for (; *str1 && (*str1 & TRIM) == (*str2 & TRIM); str1++, str2++)
  1732.     continue;
  1733.     /*
  1734.      * The following case analysis is necessary so that characters which look
  1735.      * negative collate low against normal characters but high against the
  1736.      * end-of-string NUL.
  1737.      */
  1738.     if (*str1 == '\0' && *str2 == '\0')
  1739.     return (0);
  1740.     else if (*str1 == '\0')
  1741.     return (-1);
  1742.     else if (*str2 == '\0')
  1743.     return (1);
  1744.     else
  1745.     return ((*str1 & TRIM) - (*str2 & TRIM));
  1746. } /* end StrQcmp */
  1747.  
  1748.  
  1749. /* fcompare():
  1750.  *     Comparison routine for qsort
  1751.  */
  1752. int
  1753. fcompare(file1, file2)
  1754.     Char  **file1, **file2;
  1755. {
  1756.     return (int) collate(*file1, *file2);
  1757. } /* end fcompare */
  1758.  
  1759.  
  1760. /* catn():
  1761.  *    Concatenate src onto tail of des.
  1762.  *    Des is a string whose maximum length is count.
  1763.  *    Always null terminate.
  1764.  */
  1765. void
  1766. catn(des, src, count)
  1767.     register Char *des, *src;
  1768.     register count;
  1769. {
  1770.     while (--count >= 0 && *des)
  1771.     des++;
  1772.     while (--count >= 0)
  1773.     if ((*des++ = *src++) == 0)
  1774.         return;
  1775.     *des = '\0';
  1776. } /* end catn */
  1777.  
  1778.  
  1779. /* copyn():
  1780.  *     like strncpy but always leave room for trailing \0
  1781.  *     and always null terminate.
  1782.  */
  1783. void
  1784. copyn(des, src, count)
  1785.     register Char *des, *src;
  1786.     register count;
  1787. {
  1788.     while (--count >= 0)
  1789.     if ((*des++ = *src++) == 0)
  1790.         return;
  1791.     *des = '\0';
  1792. } /* end copyn */
  1793.  
  1794.  
  1795. /* tgetenv():
  1796.  *    like it's normal string counter-part
  1797.  *    [apollo uses that in tc.os.c, so it cannot be static]
  1798.  */
  1799. Char *
  1800. tgetenv(str)
  1801.     Char   *str;
  1802. {
  1803.     Char  **var;
  1804.     int     len, res;
  1805.  
  1806.     len = Strlen(str);
  1807.     for (var = STR_environ; var != NULL && *var != NULL; var++)
  1808.     if ((*var)[len] == '=') {
  1809.         (*var)[len] = '\0';
  1810.         res = StrQcmp(*var, str);
  1811.         (*var)[len] = '=';
  1812.         if (res == 0)
  1813.         return (&((*var)[len + 1]));
  1814.     }
  1815.     return (NULL);
  1816. } /* end tgetenv */
  1817.